#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/miscdevice.h>

#define MISCLED_NAME "miscLed"
#define MISCLED_MINOR 145 // 子设备号
#define LEDOFF 0
#define LEDON 1


struct miscLed_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int led_gpio;
};

static struct miscLed_dev miscLed;

static int miscLed_gpio_init(struct device_node *nd)
{ 
    int ret = -1;

    miscLed.led_gpio = of_get_named_gpio(nd, "miscLed-gpio", 0);
    printk("miscLed: trying to use gpio %d\n", miscLed.led_gpio);
    
    if(!gpio_is_valid(miscLed.led_gpio))
    {
        printk("miscLed-gpio is not valid\n");
        return -EINVAL;
    }

    ret = gpio_request(miscLed.led_gpio, "miscLed");
    if(ret < 0)
    {
        printk("miscLed-gpio request failed, ret=%d, gpio=%d\n", ret, miscLed.led_gpio);
        return ret;
    }

    gpio_direction_output(miscLed.led_gpio, 0);
    
    return 0;
}

static int miscLed_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &miscLed;
    return 0;
}

static ssize_t miscLed_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{ 
    int ret = -1;
    unsigned char databuf[1];
    unsigned char ledstatus;

    ret = copy_from_user(databuf, buf, count);
    if(ret < 0)
    {
        printk("kernel write failed\n");
        return -EFAULT;
    }

    ledstatus = databuf[0];

    if(ledstatus == LEDON)
    {
        gpio_set_value(miscLed.led_gpio, 1);
    }
    else if(ledstatus == LEDOFF)
    {
        gpio_set_value(miscLed.led_gpio, 0);
    }
    else
    {
        printk("kernel write value is error\n");
        return -EINVAL;
    }
    return 0;
}

static struct file_operations miscLed_fops = {
    .owner = THIS_MODULE,
    .open = miscLed_open,
    .write = miscLed_write,
};

static struct miscdevice  miscLed_dev = {
     .minor = MISCLED_MINOR,
     .name = MISCLED_NAME,
     .fops = &miscLed_fops,
};

static int miscled_probe(struct platform_device *pdev)
{ 
    int ret = -1;
    struct device_node *nd = NULL;

    printk("led driver and device was matched!\r\n");

    nd = pdev->dev.of_node;

    ret = miscLed_gpio_init(nd);
    if(ret < 0)
    {
        printk("miscLed gpio init failed\n");
        return ret;
    }

    ret = misc_register(&miscLed_dev);
    if(ret < 0)
    {
        printk("miscLed register failed\n");
        goto free_gpio;
    }
    return 0;
free_gpio:
    gpio_free(miscLed.led_gpio);
    return -EINVAL;
}

static int miscled_remove(struct platform_device *pdev)
{
    gpio_set_value(miscLed.led_gpio, 0);
    gpio_free(miscLed.led_gpio);
    misc_deregister(&miscLed_dev);
    return 0;
}

static const struct of_device_id miscled_of_match[] = {
    { .compatible = "alientek,miscled" },
    { /* Sentinel */ },
};

static struct platform_driver miscled_driver = {
    .probe = miscled_probe,
    .remove = miscled_remove,
    .driver = {
        .name = "alientek,miscled",
        .of_match_table = miscled_of_match,
    },
};

static int __init miscled_init(void)
{
    return platform_driver_register(&miscled_driver);
}

static void __exit miscled_exit(void)
{
    platform_driver_unregister(&miscled_driver);
}

module_init(miscled_init);
module_exit(miscled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JACK");
MODULE_INFO(intree, "Y");

